home *** CD-ROM | disk | FTP | other *** search
- /*
- * Copyright (c) 1993 by David I. Bell
- * Permission is granted to use, distribute, or modify this source,
- * provided that this copyright notice remains intact. This program
- * is provided "as is", and I accept no responsibility for security
- * problems either inherent in the program or from its actual use.
- *
- * Program to allow specified users to execute specified commands as root.
- */
-
- #include <stdio.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <pwd.h>
- #include <unistd.h>
- #include <time.h>
-
-
- #define PRIVFILE "/etc/su1.priv"
- #define INITLOGFILE "/etc/su1.log"
- #define INITASK key_always
-
- #define ROOTNAME "root"
- #define ROOTUID 0
- #define ROOTGID 0
- #define COMMENTCHAR '#'
- #define WILDSTR "*"
-
- #define MAXARGS 10000
- #define MAXDEFINES 1000
- #define MAXPATHSIZE 1024
-
- typedef int BOOL;
- #define FALSE ((BOOL) 0)
- #define TRUE ((BOOL) 1)
-
-
- #define isblank(ch) (((ch) == ' ') || ((ch) == '\t'))
-
-
- enum keyword {
- key_null, key_define, key_paths, key_logfile, key_password,
- key_ask, key_always, key_never, key_allow, key_refuse,
- key_exact, key_prefix, key_any
- };
-
-
- struct keytable {
- char *name;
- enum keyword code;
- } keytable[] = {
- "define", key_define,
- "paths", key_paths,
- "logfile", key_logfile,
- "password", key_password,
- "ask", key_ask,
- "always", key_always,
- "never", key_never,
- "allow", key_allow,
- "refuse", key_refuse,
- "exact", key_exact,
- "prefix", key_prefix,
- "any", key_any,
- NULL, key_null
- };
-
-
- static char *initpaths[] = {
- "/bin",
- "/usr/bin",
- "/etc"
- };
-
-
- static char *privfile = PRIVFILE;
- static enum keyword ask = INITASK;
- static char *password = NULL;
- static char *logfile = NULL;
- static FILE *privfp = NULL;
- static char *myname;
- static char *paths;
- static int line;
- static int cmdargc;
- static char **cmdargv;
- static char *defines[MAXDEFINES];
-
-
- static char * makelist();
- static char * findlist();
- static char * makestring();
- static enum keyword findkeyword();
- static BOOL readline();
- static BOOL checkcommand();
- static BOOL checkusers();
- static void checkmyname();
- static void checkpassword();
- static void writelogfile();
- static void executecommand();
- static void readprivfile();
- static void examineline();
- static void badfile();
- static void parse_define();
- static void parse_paths();
- static void parse_logfile();
- static void parse_password();
- static void parse_ask();
- static void parse_allow();
- static void parse_refuse();
-
- extern char * malloc();
-
-
- main(argc, argv)
- char **argv;
- {
- if (argc <= 1) {
- fprintf(stderr, "usage: su1 command\n");
- exit(1);
- }
-
- if (geteuid() != ROOTUID) {
- fprintf(stderr, "Not running as root\n");
- exit(1);
- }
-
- cmdargc = argc - 1;
- cmdargv = argv + 1;
-
- if (cmdargc > MAXARGS - 2) {
- fprintf(stderr, "Too many arguments in command\n");
- exit(1);
- }
-
- logfile = makestring(INITLOGFILE);
-
- paths = makelist(sizeof(initpaths) / sizeof(initpaths[0]), initpaths);
-
- checkmyname();
-
- readprivfile();
-
- fprintf(stderr, "Permission denied for command\n");
- exit(1);
- }
-
-
- /*
- * Read the privilege file. If we find that the desired command can
- * be executed, we do that and so never return. If the command is
- * not found, we will return from here.
- */
- static void
- readprivfile()
- {
- char *cp;
- int argc;
- char *argv[MAXARGS];
- char buf[1024*10];
- struct stat statbuf;
-
- privfp = fopen(privfile, "r");
- if (privfp == NULL) {
- perror(privfile);
- exit(1);
- }
-
- if (fstat(fileno(privfp), &statbuf)) {
- fprintf(stderr, "Cannot stat privilege file\n");
- exit(1);
- }
-
- if ((statbuf.st_uid != ROOTUID) || (statbuf.st_mode & 022)) {
- fprintf(stderr, "Privilege file is not secure\n");
- exit(1);
- }
-
- while (readline(buf, sizeof(buf))) {
- cp = buf;
- while (isblank(*cp))
- cp++;
-
- if (*cp == COMMENTCHAR)
- continue;
-
- argc = 0;
- while (*cp) {
- if (argc >= MAXARGS)
- badfile("too many arguments");
-
- argv[argc++] = cp;
-
- while (*cp && !isblank(*cp))
- cp++;
-
- while (isblank(*cp))
- *cp++ = '\0';
- }
- examineline(argc, argv);
- }
-
- fclose(privfp);
- }
-
-
- /*
- * Read a line of the privilege file, taking into account continuation
- * characters. Returns TRUE if a line was read.
- */
- static BOOL
- readline(buf, buflen)
- char *buf;
- {
- char *cp;
- BOOL gotdata;
-
- buflen--;
- gotdata = FALSE;
-
- while (fgets(buf, buflen, privfp)) {
- gotdata = TRUE;
- line++;
-
- cp = buf + strlen(buf);
- if (cp == buf)
- badfile("missing newline character");
-
- cp--;
- if (*cp != '\n')
- badfile("line too long");
- *cp = '\0';
-
- if (cp == buf)
- return TRUE;
-
- cp--;
- if (*cp != '\\')
- return TRUE;
-
- *cp++ = ' ';
- buflen -= (cp - buf);
- buf = cp;
- }
-
- if (ferror(privfp))
- badfile("read error");
-
- if (gotdata)
- badfile("premature end of file");
-
- return FALSE;
- }
-
-
- /*
- * Examine one line of the privilege file.
- */
- static void
- examineline(argc, argv)
- char **argv;
- {
- if (argc-- <= 0)
- return;
-
- switch (findkeyword(*argv++)) {
- case key_define:
- parse_define(argc, argv);
- break;
-
- case key_paths:
- parse_paths(argc, argv);
- break;
-
- case key_logfile:
- parse_logfile(argc, argv);
- break;
-
- case key_password:
- parse_password(argc, argv);
- break;
-
- case key_ask:
- parse_ask(argc, argv);
- break;
-
- case key_allow:
- parse_allow(argc, argv);
- break;
-
- case key_refuse:
- parse_refuse(argc, argv);
- break;
-
- default:
- badfile("unknown keyword at beginning of line");
- }
- }
-
-
- static void
- parse_define(argc, argv)
- char **argv;
- {
- int i;
- char *str;
-
- if (argc <= 0)
- badfile("missing define name");
-
- str = makelist(argc, argv);
-
- for (i = 0; i < MAXDEFINES; i++) {
- if (defines[i] == NULL) {
- defines[i] = str;
- return;
- }
-
- if (strcmp(defines[i], str) == 0) {
- free(defines[i]);
- defines[i] = str;
- return;
- }
- }
- badfile("too many define strings");
- }
-
-
- static void
- parse_paths(argc, argv)
- char **argv;
- {
- int ac;
- char **av;
-
- if (argc <= 0)
- badfile("no paths specified");
-
- ac = argc;
- av = argv;
- while (ac-- > 0) {
- if (**av++ != '/')
- badfile("path names must be absolute");
- }
-
- free(paths);
- paths = makelist(argc, argv);
- }
-
-
- static void
- parse_logfile(argc, argv)
- char **argv;
- {
- if (argc <= 0)
- badfile("missing log file path");
-
- if (argc > 1)
- badfile("only one log file path can be specified");
-
- if (**argv != '/')
- badfile("log file path must be absolute");
-
- free(logfile);
- logfile = makestring(*argv);
- }
-
-
- static void
- parse_password(argc, argv)
- char **argv;
- {
- if (password)
- free(password);
- password = NULL;
-
- if (argc <= 0)
- return;
-
- if (argc > 1)
- badfile("only one password can be specified");
-
- if (strlen(*argv) < 3)
- badfile("password must be more than two characters");
-
- password = makestring(*argv);
- }
-
-
- static void
- parse_ask(argc, argv)
- char **argv;
- {
- if (argc <= 0)
- badfile("missing value for ask keyword");
-
- if (argc > 1)
- badfile("multiple values for ask keyword not allowed");
-
- ask = findkeyword(*argv);
-
- switch (ask) {
- case key_never:
- case key_always:
- return;
-
- default:
- badfile("unknown value specified for ask keyword");
- }
- }
-
-
- static void
- parse_allow(argc, argv)
- char **argv;
- {
- char *user;
- enum keyword type;
-
- if (argc < 2)
- badfile("insufficient arguments for allow keyword");
-
- user = *argv++;
- type = findkeyword(*argv++);
- argc -= 2;
-
- switch (type) {
- case key_prefix:
- case key_exact:
- if (!checkcommand(argc, argv, type))
- return;
- break;
-
- case key_any:
- break;
-
- default:
- badfile("bad command type specified for allow keyword");
- }
-
- if (!checkusers(user))
- return;
-
- /*
- * The user can execute his chosen command. Ask for and check
- * the password, write the log file entry, and finally execute
- * his command. We never return from here.
- */
- fclose(privfp);
- checkpassword();
- writelogfile();
- executecommand(type);
- exit(1);
- }
-
-
- static void
- parse_refuse(argc, argv)
- char **argv;
- {
- char *user;
- enum keyword type;
-
- if (argc < 2)
- badfile("insufficient arguments for refuse keyword");
-
- user = *argv++;
- type = findkeyword(*argv++);
- argc -= 2;
-
- switch (type) {
- case key_prefix:
- case key_exact:
- if (!checkcommand(argc, argv, type))
- return;
- break;
-
- case key_any:
- break;
-
- default:
- badfile("bad command type specified for refuse keyword");
- }
-
- if (!checkusers(user))
- return;
-
- /*
- * The user has explicitly been refused to do this command.
- * Terminate the program immediately with an error.
- */
- fprintf(stderr, "Permission denied for command\n");
- exit(1);
- }
-
-
- /*
- * Complain about a bad line in the privilege file and exit.
- */
- static void
- badfile(str)
- char *str;
- {
- fprintf(stderr, "Error in \"%s\", line %d: %s\n",
- privfile, line, str);
- exit(1);
- }
-
-
- /*
- * Check the specified string to see if it is a keyword.
- * Returns the keyword value if so, or key_null if it was not found.
- */
- static enum keyword
- findkeyword(str)
- char *str;
- {
- struct keytable *key;
-
- for (key = keytable; key->name; key++)
- if (strcmp(str, key->name) == 0)
- return key->code;
-
- return key_null;
- }
-
-
- /*
- * Append an array of words together to form a long list of strings.
- * The strings are separated by nulls, and the end of the list is
- * marked by an extra null.
- */
- char *
- makelist(argc, argv)
- char **argv;
- {
- int len;
- int ac;
- char **av;
- char *str;
- char *cp;
-
- ac = argc;
- av = argv;
-
- len = 1;
- while (ac-- > 0)
- len += strlen(*av++) + 1;
-
- str = malloc(len);
- if (str == NULL)
- badfile("malloc failed for list");
-
- cp = str;
- while (argc-- > 0) {
- strcpy(cp, *argv++);
- cp += strlen(cp) + 1;
- }
- *cp = '\0';
-
- return str;
- }
-
-
- /*
- * Make a string into a one-element list.
- */
- char *
- makestring(str)
- char *str;
- {
- int len;
- char *cp;
-
- len = strlen(str);
-
- cp = malloc(len + 2);
- if (cp == NULL)
- badfile("malloc failed");
-
- memcpy(cp, str, len + 1);
- cp[len + 1] = '\0';
-
- return cp;
- }
-
-
- /*
- * Check some command arguments to see if they match the ones the
- * user is trying to execute. Returns TRUE if so.
- */
- static BOOL
- checkcommand(argc, argv, type)
- char **argv;
- enum keyword type;
- {
- char **hisargv;
-
- if (argc <= 0)
- badfile("missing command for user keyword");
-
- hisargv = cmdargv;
-
- if (cmdargc < argc)
- return FALSE;
-
- if ((type == key_exact) && (cmdargc != argc))
- return FALSE;
-
- while (argc-- > 0) {
- if (strcmp(*argv++, *hisargv++))
- return FALSE;
- }
-
- return TRUE;
- }
-
-
- /*
- * Check our user name against a specified user name or list of user names.
- * Returns TRUE if our name is in the list.
- */
- static BOOL
- checkusers(name)
- char *name;
- {
- char *str;
-
- str = findlist(name);
- if (str == NULL)
- str = makestring(name);
-
- while (*str) {
- if (strcmp(str, WILDSTR) == 0)
- return TRUE;
-
- if (strcmp(str, myname) == 0)
- return TRUE;
-
- str += strlen(str) + 1;
- }
-
- return FALSE;
- }
-
-
- /*
- * Search for a definition for the given list name, and return the list.
- * This does not return the initial element of the list (which is the name).
- * If the list name was not found, a NULL pointer is returned.
- */
- static char *
- findlist(name)
- char *name;
- {
- int i;
- char *str;
-
- for (i = 0; i < MAXDEFINES; i++) {
- str = defines[i];
- if ((str == NULL) || strcmp(str, name))
- continue;
-
- str += strlen(str) + 1;
-
- return str;
- }
-
- return NULL;
- }
-
-
- /*
- * Get my user name and user and group ids, and verify that what we are
- * supposedly named really matches our real ids. We need to do this since
- * our user name can be compromised.
- */
- static void
- checkmyname()
- {
- struct passwd *pwd;
-
- myname = getlogin();
-
- if (myname == NULL)
- myname = cuserid(NULL);
-
- if (myname == NULL) {
- fprintf(stderr, "Cannot find your login name\n");
- exit(1);
- }
-
- pwd = getpwnam(myname);
- if (pwd == NULL) {
- fprintf(stderr, "Cannot find your password entry\n");
- exit(1);
- }
-
- if (pwd->pw_uid != getuid()) {
- fprintf(stderr, "Your user name and uid do not agree\n");
- exit(1);
- }
-
- /*
- * This check isn't done since the group id can vary for a user.
- */
- #if 0
- if (pwd->pw_gid != getgid()) {
- fprintf(stderr, "Your user name and gid do not agree\n");
- exit(1);
- }
- #endif
- }
-
-
- /*
- * Ask for the required password before allowing the command to execute.
- * This password can be one given in the privilege file, or else the real
- * root password.
- */
- static void
- checkpassword()
- {
- struct passwd *pwd;
- char *str;
-
- if (ask == key_never)
- return;
-
- if (password == NULL) {
- pwd = getpwnam(ROOTNAME);
- if (pwd == NULL) {
- fprintf(stderr, "Cannot get root password\n");
- exit(1);
- }
- password = pwd->pw_passwd;
- }
-
- if (*password == '\0')
- return;
-
- str = getpass("Password: ");
- str = crypt(str, password);
-
- if (strcmp(str, password)) {
- fprintf(stderr, "Incorrect password\n");
- exit(1);
- }
- }
-
-
- /*
- * Write out the log file.
- * There is a not-too-important race in this routine for multiple users.
- */
- static void
- writelogfile()
- {
- FILE *fp;
- int ac;
- char **av;
- time_t timebuf;
-
- if (strcmp(logfile, "/dev/null") == 0)
- return;
-
- fp = fopen(logfile, "a");
- if (fp == NULL) {
- fprintf(stderr, "Cannot open log file\n");
- exit(1);
- }
-
- time(&timebuf);
- fprintf(fp, "%-8s %16.16s ", myname, ctime(&timebuf));
-
- ac = cmdargc;
- av = cmdargv;
- while (ac-- > 0)
- fprintf(fp, " %s", *av++);
-
- fprintf(fp, "\n");
- fflush(fp);
-
- if (ferror(fp)) {
- fclose(fp);
- fprintf(stderr, "Error writing log file\n");
- exit(1);
- }
-
- if (fclose(fp)) {
- fprintf(stderr, "Error closing log file\n");
- exit(1);
- }
- }
-
-
- /*
- * Execute the user's command after setting our group and user ids.
- * The type argument specifies whether or not the user can execute
- * any command.
- */
- static void
- executecommand(type)
- enum keyword type;
- {
- int i;
- int j;
- int cmdlen;
- char *cp;
- char *args[MAXARGS];
- char execpath[MAXPATHSIZE];
-
- if (setgid(ROOTGID))
- perror("cannot set group id");
-
- if (setuid(ROOTUID))
- perror("cannot set user id");
-
- for (i = 0; i < cmdargc; i++)
- args[i] = cmdargv[i];
-
- args[i] = NULL;
-
- /*
- * If the command type allows any command to be executed, then
- * simply do that, using the user's own PATH specification.
- * This is not a security problem, since the user was explicitly
- * allowed to execute any program.
- */
- if (type == key_any) {
- execvp(args[0], args);
- fprintf(stderr, "Failed to execute command\n");
- exit(1);
- }
-
- /*
- * The command is restricted to a limited set of commands run from
- * a limited number of directories. See if the command name matches
- * one of the define strings. If so, then replace the command name
- * with the string definition.
- */
- cp = findlist(cmdargv[0]);
- if (cp) {
- i = 0;
- while (*cp) {
- args[i++] = cp;
- cp += strlen(cp) + 1;
- }
-
- for (j = 1; j < cmdargc; j++) {
- if (i >= MAXARGS - 1) {
- fprintf(stderr, "Too many arguments\n");
- exit(1);
- }
- args[i++] = cmdargv[j];
- }
- args[i] = NULL;
- }
-
- /*
- * If the command name contains a slash, then make sure that it
- * is an absolute path, and then try to execute that exact program.
- */
- if (strchr(args[0], '/')) {
- if (args[0][0] != '/') {
- fprintf(stderr, "Command is not an absolute pathname\n");
- exit(1);
- }
- execv(args[0], args);
- fprintf(stderr, "Failed to execute command\n");
- exit(1);
- }
-
- /*
- * The command name is a generic name, so search down our own
- * internal path list, trying to execute the program from each path.
- */
- cmdlen = strlen(args[0]);
-
- for (cp = paths; *cp; cp += strlen(cp) + 1) {
- if (strlen(cp) + cmdlen > MAXPATHSIZE - 2)
- badfile("path name of command is too long");
-
- strcpy(execpath, cp);
- strcat(execpath, "/");
- strcat(execpath, args[0]);
-
- execv(execpath, args);
- }
-
- fprintf(stderr, "Failed to execute command\n");
- exit(1);
- }
-
- /* END CODE */
-